
import type { componentInstance, container, vnode, createRenderer, vnodeProps, vnodeChildren } from './index.d'
import { createComponentInstance, setupComponent } from './component';
import { ShapeFlags } from '../shared/shapeFlags';
import { Fragment, Text } from './vnode';
import { createAppAPI } from './createApp';
import { effect } from '../reactivity/effect';
import { shouldUpdateComponent } from './componentUpdateUtils';
import { queueJob } from './scheduler';

/**可以让用户自定义渲染器  （默认是DOM渲染，可以自己定义写成canvas渲染等）
 * @param options 用户传入的自定义渲染函数
 * @returns  在这里返回createApp函数，让createApp的时候能够用到render
 */
export function createRenderer(options: createRenderer) {
    const { createElement, patchProp, insert, remove, setElementText } = options  //对应的默认DOM渲染器的代码，在runtime-dom中

    //#region 在这里给函数改名，这样我们就能知道，如果出错了，是哪个函数出错了。 其实可以在解构的时候改名，但是这样无法添加jsDoc注释，不方便阅读代码

    /**用户自定义 - 创建元素函数 - 默认是DOM创建
     * @param vnodeType 虚拟节点type，比如div等
     * @returns 返回创建的元素，一般是DOM，或者自定义的内容
     */
    const hostCreateElement = createElement
    /**用户自定义 - 挂载属性函数 - 默认是DOM属性挂载
     * @param el 被创建出来的元素，一般是DOM，或者自定义的内容 
     * @param key 属性的键
     * @param preVal 旧值 （可能为null， 说明是初始化） 
     * @param nextVal 新值 （可能为null， 说明属性被删除了） 
     */
    const hostPatchProp = patchProp
    /**用户自定义 - 插入元素函数 - 默认是DOM插入
     * @param child 要插入的元素，一般是DOM，或者自定义的内容  
     * @param parent 被插入的父元素
     * @param anchor 锚点，用于指定插入到哪个元素前面 
     */
    const hostInsert = insert
    /**用户自定义 - 移除单个孩子 - 默认是DOM移除
     * @param el 要被移除的元素，一般是DOM
     */
    const hostRemove = remove
    /**用户自定义 - 将元素的孩子设置为text - 默认是设置为DOM的text
     * @param el 要被设置的元素
     * @param text 元素的孩子（字符串类型） 
     */
    const hostSetElementText = setElementText

    //#endregion

    //#region 内部函数

    /**render函数 。只有在createApp的时候才调用
     * @param vnode 虚拟节点
     * @param container 容器
     * @param parentComponent 父组件实例
    */
    function render(vnode: vnode, container: container) {
        //调用patch
        patch(null, vnode, container, undefined as any, null)//这里需要第四五个参数，但是这个函数是app根组件调用的，所以这俩都是没有的（懒得改那么多函数的ts报错了，直接any这里了）
    }

    /**patch函数，最重要的函数，会对比n1和n2，来判断是需要更新还是创建，操作的是Element类型还是Component类型，然后进行递归创建/更新节点 
     * @param n1 旧Vnode  （如果是初始化，这个就是null）
     * @param n2 新Vnode
     * @param container 容器
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function patch(n1: vnode | null, n2: vnode, container: container, parentComponent: componentInstance, anchor: container | null) {
        const { shapeFlag, type } = n2
        switch (type) {
            case Fragment:
                processFragment(n1, n2, container, parentComponent, anchor!)// 这里用 ！，因为懒得改ts报错
                break;
            case Text:
                processText(n1, n2, container)
                break;
            default://没有命中的。说明不是特殊标签，进行正常的element或component渲染
                if (shapeFlag & ShapeFlags.ELEMENT) {//位运算判断是否是element
                    processElement(n1, n2, container, parentComponent, anchor!) // 这里用 ！，因为懒得改ts报错
                } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {//位运算判断是否是组件
                    processComponent(n1, n2, container, parentComponent, anchor!)// 这里用 ！，因为懒得改ts报错
                }
                break;
        }
    }

    /**处理组件类型，分为初始化和更新俩种
     * @param n1 旧虚拟节点 （如果是初始化，这个就是null）
     * @param n2 新虚拟节点
     * @param container 容器
     * @param parentComponent 父组件实例 
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function processComponent(n1: vnode | null, n2: vnode, container: container, parentComponent: componentInstance, anchor: container) {
        if (!n1) {
            mountComponent(n2, container, parentComponent, anchor);
        } else {
            updateComponent(n1, n2, container)
        }

    }
    /**初始化组件 
     * @param initialVNode 初始化的Vnode
     * @param container 容器
     * @param parentComponent 父组件实例 
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function mountComponent(initialVNode: vnode, container: container, parentComponent: componentInstance, anchor: container) {
        /**获得组件实例 */
        const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent))//顺便给 vnode.component 赋值
        setupComponent(instance)
        setupRenderEffect(instance, initialVNode, container, anchor)
    }
    /**组件的更新 
     * @param n1 旧vnode
     * @param n2 新vnode
     * @param container 容器DOM
     */
    function updateComponent(n1: vnode, n2: vnode, container: container) {
        console.log("更新组件", n1, n2);
        /**更新后的组件实例*/
        const instance = (n2.component = n1.component)!;//同时，顺便给新n2的component赋值

        if (shouldUpdateComponent(n1, n2)) { // 先看看这个组件是否应该更新
            instance.next = n2;// 那么 next 就是新的 vnode 了（也就是 n2）   
            instance.update?.()//需要的话就手动调用之前的effect的runner ，调用 update 再次更新调用 patch 逻辑， 在update 中调用的 next 就变成了 n2了
        } else {// 不需要更新的话，那么只需要覆盖下面的属性即可 
            n2.component = n1.component;
            n2.el = n1.el;
            instance.vnode = n2;
        }
    }
    /**组件-setupRenderEffect：在里面赋值instance.update、调用render、收集触发依赖、触发生命周期、递归调用patch 
     * @param instance 当前组件实例
     * @param initialVNode 初始化的VNode
     * @param container 容器DOM
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function setupRenderEffect(instance: componentInstance, initialVNode: vnode, container: container, anchor: container) {
        instance.update = effect(() => { //使用effect包裹，这样当响应式值改变了，就会再次触发这个函数  
            if (!instance.isMounted) {//如果还未挂载，就走初始化逻辑 
                const { proxy } = instance

                /**render函数返回的虚拟节点树 */  //同时给 instance.subTree 赋值，方便更新的时候拿到
                const subTree = (instance.subTree = instance.render!.call(proxy)) //在这里绑定this 。 proxy的初始化在 src\runtime-core\component.ts 的 setupStatefulComponent函数

                //调用patch，处理子组件   vnode->patch    vnode -> element -> mountElement
                patch(null, subTree, container, instance, anchor) //这里的父组件就是instance了，因为接下来在递归子组件 

                //在这里给el赋值。因为我们需要保证全部子组件都渲染了，这时候赋值的el才是根元素 （我们在 mountElement 函数中就不断的在给每个vnode赋值它的el，在最后，给组件赋值根节点）
                initialVNode.el = subTree.el
                instance.isMounted = true
            } else {// 否则走更新逻辑  
                const { proxy, next, vnode } = instance
                if (next) {//next是下一次的虚拟节点，vnode是当前的虚拟节点
                    next.el = vnode.el //更新next的el
                    updateComponentPreRender(instance, next)
                }
                /**原本的节点树 */
                const preSubTree = instance.subTree
                /**拿到更新后的节点树 */
                const subTree = instance.render!.call(proxy) //步骤含义和上面初始化时相同
                instance.subTree = subTree //在这里更新一下节点树
                patch(preSubTree, subTree, container, instance, anchor)
            }

        }, { //使用 scheduler 来控制更新，for循环更新响应式100次，只让最后一次触发组件更新 （即，视图更新需要异步，否则将会影响其它代码）
            scheduler: () => {
                queueJob(instance.update!)
            }
        })
    }
    /**更新组件实例instance，从nextVnode上取出数据更新
     * @param instance 组件实例
     * @param nextVNode 更新之后的虚拟节点
     */
    function updateComponentPreRender(instance: componentInstance, nextVNode: vnode) {
        nextVNode.component = instance;
        // const prevProps = instance.vnode.props;// 所以之前的 props 就是基于 instance.vnode.props 来获取 
        instance.vnode = nextVNode;//更新当前vnode
        instance.next = null; //清空next 
        const { props } = nextVNode;
        instance.props = props || {};
    }




    /**处理element类型，分为初始化和更新俩种
     * @param n1 旧vNode （如果是初始化，这个就是null）
     * @param n2 新Vnode
     * @param container 容器
     * @param parentComponent 父组件实例 
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function processElement(n1: vnode | null, n2: vnode, container: container, parentComponent: componentInstance, anchor: container) {
        if (!n1) {
            mountElement(n2, container, parentComponent, anchor)
        } else {
            patchElement(n1, n2, container, parentComponent, anchor)
        }
    }
    /**初始化element 
     * @param vnode vnode
     * @param container 容器
     * @param parentComponent 父组件实例 
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function mountElement(vnode: vnode, container: container, parentComponent: componentInstance, anchor: container) {
        if (typeof vnode.type !== 'string') return
        /**创建的DOM元素 */
        const el = (vnode.el = hostCreateElement(vnode.type)) //创建元素，比如创建 div 、 h1 等，同时给当前的vnode.el赋值
        const { children, props, shapeFlag } = vnode
        //赋值元素内部内容。children类型可能是vnode[] 或者 string   
        if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { //是string的话
            el.textContent = children as string  // 这里 children 就是 text ，只需要用DOM操作渲染一下就完事了
        } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {//如果是虚拟节点数组，那么就要调用patch
            // 举个栗子
            // render(){ 
            //     return h("div",{},[h("p"),h(Hello)])  //Hello 是个 component
            // }
            // 这里 children 就是个数组了，就需要依次调用 patch 递归来处理
            mountChildren(vnode.children, el, parentComponent, anchor)
        }
        //赋值元素属性
        for (const key in props) {
            if (Object.prototype.hasOwnProperty.call(props, key)) {
                const value = props[key];
                hostPatchProp(el, key, null, value) // 为了实现用户自定义渲染接口
            }
        }
        hostInsert(el, container, anchor)  //原本是 container.append(el)，但是为了实现用户自定义渲染接口，所以变成这个
    }
    /**更新Element节点 
     * @param n1 旧vNode
     * @param n2 新Vnode
     * @param container 容器
     * @param parentComponent 父组件 
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function patchElement(n1: vnode, n2: vnode, container: container, parentComponent: componentInstance, anchor: container) {
        const oldProps = n1.props || {}
        const newProps = n2.props || {}
        /**该节点的DOM */
        const el = (n2.el = n1.el as container)  //获取el的同时，给n2的el也赋值，这样才能让下一次更新时也能拿到el
        patchProps(el, oldProps, newProps)
        patchChildren(n1, n2, el, parentComponent, anchor)
    }
    /**更新element的props
     * @param el 该element的el，用于挂载属性等
     * @param oldProps 旧props 
     * @param newProps 新props
     */
    function patchProps(el: container, oldProps: vnodeProps, newProps: vnodeProps) {
        if (oldProps === newProps) return //完全相同就别动，免得浪费性能 （但是这个对比好像没啥用啊？）


        //更新分为下面几种情况
        // 1. 原属性值从 foo 变成 foo-new ，就是普通的更新
        // 2. foo -> undefined 或 null，变成了这种无意义的值，就应该删除
        // 3. newProps对象中，少了oldProps对象中的某个属性，那也是删除
        // 4. newProps对象中比oldProps对象多了某个属性，就是新增

        // oldProps 有，newProps 也有，但是 val 值变更了
        // 举个栗子，之前: oldProps.id = 1 ，更新后：newProps.id = 2 
        // key 存在 oldProps 里，也存在 newProps 内，所以，以newProps为基准 
        for (const key in newProps) {//先遍历 newProps ，在这里判断删除/修改
            /**根据新props的键值，拿到oldProps中对应的旧props  （可能为空，就是被删除了） */
            const preProp = oldProps[key]
            /**新属性 */
            const nextProp = newProps[key]
            if (preProp !== nextProp) { //如果新旧不相同，那就是更新或删除
                hostPatchProp(el, key, preProp, nextProp)
            }
        }

        // oldProps 有，而 newProps 没有了
        // 比如，之前： {id:1,tId:2}  更新后： {id:1}
        // 这种情况下我们就应该以 oldProps 作为基准，因为在 newProps 里面是没有的 tId 的
        // 还需要注意一点，如果这个 key 在 newProps 里面已经存在了，说明已经处理过了，就不要在处理了 
        for (const key in oldProps) { //然后遍历老props，把新的没有的删掉
            const prevProp = oldProps[key];
            const nextProp = null; // 这里是以oldProps为基准来遍历，而且得到的值是newProps内没有的，所以交给host更新的时候，把新的值设置为null
            if (!(key in newProps)) {// 还需要注意一点，如果这个 key 在 newProps 里面已经存在了，说明已经处理过了，就不要在处理了 
                hostPatchProp(el, key, prevProp, nextProp);
            }
        }
    }



    /**对比children 
     * @param n1 旧vNode
     * @param n2 新Vnode
     * @param container 父亲容器，用于删除/设置children的DOM节点
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function patchChildren(n1: vnode, n2: vnode, container: container, parentComponent: componentInstance, anchor: container) {
        //对比children，有四种情况 （children有两种类型，text的字符串类型或者vnode[]数组类型，所以搭配出四种变化）
        // 1. vnode[] -> string
        // 2. string  -> string
        // 3. string  -> vnode[]
        // 4. vnode[] -> vnode[]


        /**老节点的shapeFlag */
        const preShapeFlag = n1.shapeFlag
        /**老节点的children */
        const c1 = n1.children
        /**新节点的shapeFlag */
        const shapeFlag = n2.shapeFlag
        /**新节点的children */
        const c2 = n2.children


        if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { //如果新节点是text节点

            //下面这一块的代码可以精简，因为 if和else中，调用hostSetElementText 是一模一样的，但是为了可读性，我就不重构了

            if (preShapeFlag & ShapeFlags.ARRAY_CHILDREN) { //老节点是数组， 说明是情况 1. vnode[] -> string

                //把老的children全部移除
                unmountChildren(n1.children as vnode[]) //这时候children类型一定是 vnode[]
                //然后设置为新的text
                hostSetElementText(container, c2 as string)

            } else {//否则老节点就是string，说明是情况 2. string  -> string 
                if (c2 !== c1) { //不相同时才设置
                    hostSetElementText(container, c2 as string)
                }
            }

        } else { //如果新节点是数组类型

            if (preShapeFlag & ShapeFlags.TEXT_CHILDREN) { //如果老节点是string， 说明是情况 3. string  -> vnode[]

                hostSetElementText(container, "") //把老节点的文本设为空
                mountChildren(c2, container, parentComponent, anchor);//然后挂载新的children

            } else {//如果老节点是数组，说明是情况 4. vnode[] -> vnode[]，就需要使用diff算法了 
                patchKeyedChildren(c1 as vnode[], c2 as vnode[], container, parentComponent, anchor)
            }

        }
    }
    /**取消挂载Children。实际上就是获取children[i].el，然后移除DOM
     * @param children 虚拟节点的children，是个数组
     */
    function unmountChildren(children: vnode[]) {
        for (let i = 0; i < children.length; i++) {
            /**拿到children的el （DOM元素） */
            const { el } = children[i];
            if (el) hostRemove(el)
        }
    }
    /**挂载children，在这里遍历vnode.children数组，每个调用一次patch 。这时是确定children是个数组了的
     * @param children 要被挂载的children
     * @param container 挂载的容器DOM元素
     * @param parentComponent 父组件实例 
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function mountChildren(children: vnodeChildren | undefined, container: container, parentComponent: componentInstance, anchor: container) {
        if (!children || typeof children === 'string') return
        children.forEach((v) => {
            patch(null, v, container, parentComponent, anchor)
        })
    }
    /**对比两个 children 数组  。diff算法就在这里
     * @param c1 旧children数组
     * @param c2 新children数组
     * @param container 容器DOM元素
     * @param parentComponent 父组件实例
     * @param parentAnchor 父插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function patchKeyedChildren(c1: vnode[], c2: vnode[], container: container, parentComponent: componentInstance, parentAnchor: container) {
        /**左边界索引 */
        let i = 0;
        /**新children数组的长度 */
        const l2 = c2.length;
        /**旧children数组的右边界索引 */
        let e1 = c1.length - 1;
        /**新children数组的右边界索引 */
        let e2 = l2 - 1;

        /**判断两个VNode是否相同 (基于 type 和 key 判断是否相等) */
        const isSameVNodeType = (n1: vnode, n2: vnode) => {
            return n1.type === n2.type && n1.key === n2.key;
        };

        // 先从左到右，找到左边界。 判断 n1 和 n2 是否相等，遇到不相等就跳出。当前的相等，就进行patch递归
        while (i <= e1 && i <= e2) {
            /**当前索引对应的旧孩子 */
            const n1 = c1[i]
            /**当前索引对应的新孩子 */
            const n2 = c2[i]
            if (!isSameVNodeType(n1, n2)) { //如果不相同
                console.log("两个 child 不相等(从左往右比对)，左边界为", i, `\n旧孩子: `, n1, `\n新孩子: `, n2);
                break
            }
            patch(n1, n2, container, parentComponent, parentAnchor) //进行patch递归
            i++
        }

        //此时的i不再开始变化，而是 e1 和 e2 从右往左进行对比 （从后往前判断，看看是否屁股后也有相同的元素，免得插入了中间一个，后面的没变却要重新渲染）
        //遇到不相等就跳出。当前的相等，就把这两个child节点进行patch递归
        while (i <= e1 && i <= e2) {
            /**当前索引对应的旧孩子 */
            const n1 = c1[e1]
            /**当前索引对应的新孩子 */
            const n2 = c2[e2]
            if (!isSameVNodeType(n1, n2)) { //如果不相同
                console.log(`两个 child 不相等(从右往左比对)，右边界分别为 旧 ${e1} 新 ${e2}`, `\n旧孩子: `, n1, `\n新孩子: `, n2);
                break
            }
            patch(n1, n2, container, parentComponent, parentAnchor) //进行patch递归
            e1--
            e2--
        }

        console.log('边界确定', `i = ${i}, e1 = ${e1}`, `e2 = ${e2}`);

        if (i > e1 && i <= e2) {
            // 如果是这种情况的话就说明, 新节点的数量大于旧节点的数量 ==> 也就是说新增了 vnode   (画图可以就理解为什么i在这俩中间)

            // 锚点的计算：新的节点有可能需要添加到尾部，也可能添加到头部，所以需要指定添加的问题

            /**判断要插入锚点的位置 */
            const nextPos = e2 + 1 //应该使用e2 + 1  （而不是 i + 1，因为对于往左侧添加的话，当插入的数量≥2的时候，应该获取到 c2 的第一个元素，i+1就不准确了，可以画图看）
            /**获得要插入的锚点 */
            const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor; // 如果比新children长度短，就正常以这个为锚点。 否则就越界了，应该使用父亲的Anchor

            while (i <= e2) {
                console.log(`需要新创建一个 vnode:`, c2[i]);
                patch(null, c2[i], container, parentComponent, anchor as container) //第一个参数为null，代表是创建新节点
                i++; // 光标移动，支持一次新建多个节点 
            }
        } else if (i <= e1 && i > e2) {
            // 这种情况的话说明新节点的数量是小于旧节点的数量的，那么我们就需要把多余的删除  
            while (i <= e1) {
                console.log(`需要删除当前的 vnode:  `, c1[i].el);
                hostRemove(c1[i].el);
                i++;
            }
        } else {
            //剩下的就是中间对比了， 可以是改顺序了、删除了、新增了等
            //最普通的想法就是遍历中间所有节点，然后一个个去改，但是时间复杂度就是O(n)了，所以可以采用 key 来进行
            //同样也分为多种情况
            // 情况1，总节点数目相同，需要删除老的。a,b,(c,d),f,g --> a,b,(e,c),f,g  
            // 情况2，新节点数目比旧节点少，已经对比完新节点，却还存在老节点没对比，可以直接删除老的 a,b,(e,c,d),f,g --> a,b,(e,c),f,g 
            // 情况3，顺序变化。 a,b,(c,d,e),f,g --> a,b,(e,c,d),f,g 
            // 情况4，节点新增。 a,b,(c,e),f,g --> a,b,(e,c,d),f,g

            /**旧children数组的 中间区间 的左边界 */
            let s1 = i
            /**新children数组的 中间区间 的左边界 */
            let s2 = i

            /**需要处理新节点的数量 */
            const toBePatched = e2 - s2 + 1; // 这个可以用于优化情况2
            /**老节点已经被patch处理的数量 */
            let patched = 0; // 这个可以用于优化情况2

            // 怎么判断当前这中间部分，是否有节点需要移动呢 -> 如果发现 所有节点的旧index都是升序的话，说明不需要移动
            //  比如 a,b,(c,d,e),f,g --> a,b,(e,c,d),f,g  ，其中cde的索引从 0 1 2 变成了 1 2 0，不是递增的，所以需要移动
            /**是否有节点需要移动 - 标识符。为true的时候会使用最长递增子序列来进行移动 */
            let moved = false;
            /**当前新索引中，升序部分最大的值，用于判断索引是否为升序 */
            let maxNewIndexSoFar = 0;

            /**根据key查找index的映射Map，键是key，值是index */
            const keyToNewIndexMap = new Map<string | number, number>();
            /**- 新索引与旧索引的映射关系。 
             * - 初始化为0, 后面处理的时候如果发现是0的话，那么就说明新值在老的里面不存在
             * - 在“遍历旧孩子们”的时候进行修改
             */
            const newIndexToOldIndexMap: number[] = new Array(toBePatched).fill(0);  //

            //遍历新孩子们，设置 key 与 index 的映射
            for (let j = s2; j <= e2; j++) {
                /**当前索引对应的新孩子 */
                const nextChild = c2[j]
                if (nextChild.key) keyToNewIndexMap.set(nextChild.key, j) //用户有传入key才存
            }

            //遍历旧孩子们
            for (let j = s1; j <= e1; j++) {
                /**当前索引对应的旧孩子 */
                const preChild = c1[j]

                if (patched >= toBePatched) {//优化情况2
                    hostRemove(preChild.el)
                    continue//下面的就不需要执行了，优化算法速度
                }

                /**这个旧节点的新索引 */
                let newIndex: number | undefined
                if (preChild.key !== null || preChild.key !== undefined) { //用户有传入key才获取 
                    newIndex = keyToNewIndexMap.get(preChild.key!) //从map中取出对应的索引  
                } else {// 用户有可能没有传入key，就用索引代替
                    for (let k = s2; k <= e2; k++) { //这时候就需要遍历了。 注意k <= e2，不要忘记等于号了
                        if (isSameVNodeType(preChild, c2[k])) {//判断 preChild 和 新孩子[k] 是否相等
                            newIndex = j
                            break
                        }
                    }
                }

                if (newIndex === undefined) { //如果经过上面的查找key，还没找到对应的索引，说明这个旧节点在新孩子数组中不存在，应该删除
                    hostRemove(preChild.el) //移除掉这个DOM
                } else {//存在的话就patch，进行更深层次的更新

                    //根据newIndex是否升序，判断是否有节点需要移动
                    if (newIndex >= maxNewIndexSoFar) {//如果一直是升序的
                        maxNewIndexSoFar = newIndex
                    } else {//一旦发现有的不是升序，就认定有节点需要移动，启动最长递增子序列算法
                        moved = true
                    }

                    //在这里给 newIndexToOldIndexMap 赋值。 这时候的j就是旧index
                    // newIndex - s2 的含义：因为我们的数组索引是从“变化区域的左侧”开始，而newIndex的索引是从“整个children的左侧”开始，所以需要相减，才是我们真正想赋值的
                    // j + 1 的含义：因为j是从0开始，而在这个数组中，0是有特殊含义的(代表新值在老的里不存在)
                    newIndexToOldIndexMap[newIndex - s2] = j + 1

                    patch(preChild, c2[newIndex], container, parentComponent, null) //进行patch更新节点

                    patched++ //被处理过的值++
                }

            }

            //使用最长递增子序列优化：因为如果有部分元素是升序的话，那么这些元素就是不需要移动的，只需要移动乱序的即可。
            /** 若有节点需要移动，得到最长递增子序列(下标数组)，仅对移动过的节点处理 */
            const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [] // 通过 moved 来进行优化，如果没有移动过的话 那么就不需要执行算法（这个算法也是有些时间复杂度的）
            /**最长递增子序列的指针，从 (increasingNewIndexSequence.length - 1) 开始逆序遍历 */
            let point = increasingNewIndexSequence.length - 1

            //处理 顺序移动或新增 的情况。   使用逆序遍历，才能保证插入时的锚点已经被固定!!!
            for (let j = toBePatched - 1; j >= 0; j--) {
                // 假设toBePatched是5 ， 此时的递增子序列 increasingNewIndexSequence 是[1, 2, 4] ， point和j从末尾开始逆序
                // j=4时，point=2，递增子序列[point] = 4， j === 4，则point--
                // j=3时，ponit=1，递增子序列[point] = 2， j !== 2，则移动位置
                // j=2时，ponit=1，递增子序列[point] = 2， j === 2，则point--
                // j=1时，point=0，递增子序列[point] = 1， j === 1，则point--


                /**确定当前要处理的节点索引 */
                const nextIndex = s2 + j; // 因为j是 “中间区间” 的索引，而我们要拿到的孩子应该是在整个children数组中的索引
                /**根据当前要处理的索引，拿到新孩子数组中指定的孩子 */
                const nextChild = c2[nextIndex];
                /**要插入的锚点。锚点等于当前节点索引+1，也就是当前节点的后面一个节点。 因为是倒遍历，所以锚点是位置确定的节点，不会变动了 */
                const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor; //  判断是否越界，越界了就用默认的

                if (newIndexToOldIndexMap[j] === 0) {//如果当前值是0，说明要创建

                    patch(null, nextChild, container, parentComponent, anchor)//进行patch创建新节点

                } else if (moved) {//需要移动的时候才

                    if (j !== increasingNewIndexSequence[point] || point < 0) {//除了判断j是否不相等， 还有point小于0的时候，就直接移动就行
                        //如果当前j和递增子序列的值不相同，说明是“非递增子序列”，需要移动位置
                        console.log(nextChild.el, '这个节点要移动位置');
                        hostInsert(nextChild.el!, container, anchor) //在指定锚点的前面插入DOM，即 container.insertBefore(el, anchor)
                    } else {
                        point-- //不需要移动的话，说明匹配成功递增子序列了，就 point-- 即可
                    }

                }


            }

        }



    }


    /**处理Fragment标签 （只渲染孩子）
     * @param n1 旧虚拟节点 （如果是初始化，这个就是null）
     * @param n2 新虚拟节点
     * @param container 容器DOM
     * @param parentComponent 父组件实例 
     * @param parentAnchor 父组件的插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function processFragment(n1: vnode | null, n2: vnode, container: container, parentComponent: componentInstance, anchor: container) {
        if (n2.children) mountChildren(n2.children, container, parentComponent, anchor) //只渲染孩子
    }
    /**处理Text文本节点 （只渲染文字，不渲染标签）
     * @param n1 旧虚拟节点 （如果是初始化，这个就是null）
     * @param n2 新虚拟节点
     * @param container 容器
     */
    function processText(n1: vnode | null, n2: vnode, container: container) {
        const { children } = n2  //这里的 children 就是用户传来的字符串Text
        const textNode = (n2.el = document.createTextNode(children as string) as any) //记得顺便给vnode.el赋值 （在mountElement的时候也做了这事，由于无法复用，所以这里也要这么做）
        container.append(textNode)
    }

    //#endregion

    return { //在这里返回createApp函数，让createApp的时候能够用到render
        createApp: createAppAPI(render)
    }
}


/**获得最长递增子序列的索引数组
 * @param arr 要被查找的数组  
 * @returns 返回最长子序列的下标数组。 比如 传入[4,2,3,1,5] ，得到 [1,2,4]  （即最长递增序列是 2 3 5）
 */
function getSequence(arr: number[]): number[] {
    const p = arr.slice();
    const result = [0];
    let i: number, j: number, u: number, v: number, c: number;
    const len = arr.length;
    for (i = 0; i < len; i++) {
        const arrI = arr[i];
        if (arrI !== 0) {
            j = result[result.length - 1];
            if (arr[j] < arrI) {
                p[i] = j;
                result.push(i);
                continue;
            }
            u = 0;
            v = result.length - 1;
            while (u < v) {
                c = (u + v) >> 1;
                if (arr[result[c]] < arrI) {
                    u = c + 1;
                } else {
                    v = c;
                }
            }
            if (arrI < arr[result[u]]) {
                if (u > 0) {
                    p[i] = result[u - 1];
                }
                result[u] = i;
            }
        }
    }
    u = result.length;
    v = result[u - 1];
    while (u-- > 0) {
        result[u] = v;
        v = p[v];
    }
    return result;
}